JPA

[JPA] 컬렉션 패치조인 페이징 하기

Posted by Songi on 2021-01-12

“컬렉션 패치조인 페이징 하기”


엔티티와 샘플 데이터

  • Order.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Entity
@Table(name = "orders")
public class Order {

@Id @GeneratedValue
@Column(name = "order_id")
private Long id;

@ManyToOne(fetch = FetchType.LAZY)
@JoinColumn(name = "member_id")
private Member member;

@OneToOne(fetch = FetchType.LAZY, cascade = CascadeType.ALL)
private Delivery delivery;

@OneToMany(mappedBy = "order", cascade = CascadeType.ALL)
private List<OrderItem> orderItems = new ArrayList<>();

// ...
}
  • 샘플 데이터

  • OrderItem.java

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18

    @Entity
    public class OrderItem {

    @GeneratedValue @Id
    @Column(name = "order_item_id")
    private Long id;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "order_id")
    private Order order;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "item_id")
    private Item item;

    // ...
    }
  • 샘플 데이터

  • Item.java
1
2
3
4
5
6
7
8
9
10
11
@Entity
public abstract class Item {

@Id
@GeneratedValue
@Column(name = "item_id")
private Long id;

// ...

}
  • 샘플 데이터


컬렉션 패치 조인

  • 컬렉션을 패치 조인 하면 일대다 조인이 발생하므로 데이터가 예측할수 없이 증가.

  • 실행결과


페이징을 추가한다면?

1
2
3
4
5
6
7
8
9
10
11
public List<Order> findAllWithItem() {
return em.createQuery(
"select distinct o from Order o" +
" join fetch o.member m" +
" join fetch o.delivery d" +
" join fetch o.orderItems oi" +
" join fetch oi.item i", Order.class
).setFirstResult(1)
.setMaxResults(100)
.getResultList();
}
  • 페이징 쿼리 안나가고 있음.

  • 하이버네이트는 경고 로그를 남김.
  • DB 데이터를 읽어 메모리에서 페이징을 시도.

왜?

  • 패치조인 후 DB에서 페이징 처리 시 다의 관계 데이터 기준으로 페이징 하게 되고 이는 의도한 바와 다르기 때문.
  • 메모리에 데이터를 다 들고 오게 되므로 최악의 경우 장애로 이어질 수 있음.


해결책

@BatchSize, default_batch_fetch_size

다음의 순서로 컬렉션 패치 조인과 페이징 진행

  1. XToOne 관계 패치 조인
  • row 수 증가 시키지 않으므로 페이징 쿼리에 영향 주지 않음

  1. 컬렉션 지연 로딩 조회
  • hibernate.default_batch_fetch_size, @BatchSize 적용

  • 프록시 객체를 한번에 설정한 size 만큼 in 쿼리로 조회